use gl::types::*;
use gl_rs::utils::camera::Camera;
use gl_rs::utils::common::{process_events, process_input};
use gl_rs::utils::shader::Shader;
#[allow(non_upper_case_globals)]
use glfw::Context;

use cgmath::prelude::*;
use cgmath::{perspective, vec3, Deg, Matrix4, Point3, Rad, Vector3};
use image::GenericImage;
use std::os::raw::c_void;
use std::path::Path;
use std::{mem, ptr};

const SCR_WIDTH: u32 = 960;
const SCR_HEIGHT: u32 = 544;

#[allow(non_snake_case)]
pub fn main_2_1_1() {
    let mut glfw = glfw::init(glfw::FAIL_ON_ERRORS).unwrap();
    glfw.window_hint(glfw::WindowHint::ContextVersion(3, 3));
    glfw.window_hint(glfw::WindowHint::OpenGlProfile(glfw::OpenGlProfileHint::Core));
    #[cfg(target_os = "macos")]
    glfw.window_hint(glfw::WindowHint::OpenGlForwardCompat(true));

    let (mut window, events) = glfw
        .create_window(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", glfw::WindowMode::Windowed)
        .expect("Failed to create GLFW window");
    window.make_current();
    window.set_framebuffer_size_polling(true);
    window.set_cursor_pos_polling(true);
    window.set_scroll_polling(true);

    window.set_cursor_mode(glfw::CursorMode::Disabled);

    gl::load_with(|symbol| window.get_proc_address(symbol) as *const _);

    let mut camera = Camera {
        position: Point3::new(0., 0., 3.),
        ..Camera::default()
    };

    let mut delta_time: f32;
    let mut last_frame: f32 = 0.0;

    let mut first_mouse = true;
    let mut last_x = SCR_WIDTH as f32 / 2.0;
    let mut last_y = SCR_HEIGHT as f32 / 2.0;

    let light_position = vec3(1.2, 1., 2.);

    let (light_shader, lamp_shader, VBO, cube_VAO, light_VAO) = unsafe {
        gl::Enable(gl::DEPTH_TEST);
        gl::Enable(gl::POLYGON_SMOOTH);
        let light_shader = Shader::new(
            "src/_2_lighting/shaders/1.1.colors.vert",
            "src/_2_lighting/shaders/1.1.colors.frag",
        );
        let lamp_shader = Shader::new(
            "src/_2_lighting/shaders/1.1.lamp.vert",
            "src/_2_lighting/shaders/1.1.lamp.frag",
        );
        let vertices: [f32; 108] = [
            //
            -0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, -0.5, -0.5, -0.5, -0.5,
            //
            -0.5, -0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5,
            //
            -0.5, 0.5, 0.5, -0.5, 0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, 0.5, -0.5, 0.5, 0.5,
            //
            0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, -0.5, 0.5, 0.5, 0.5, 0.5,
            //
            -0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5, -0.5, -0.5, 0.5, -0.5, -0.5, -0.5,
            //
            -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5, -0.5,
        ];

        let (mut VBO, mut cube_VAO) = (0, 0);
        gl::GenVertexArrays(1, &mut cube_VAO);
        gl::GenBuffers(1, &mut VBO);

        gl::BindVertexArray(cube_VAO);

        gl::BindBuffer(gl::ARRAY_BUFFER, VBO);
        gl::BufferData(
            gl::ARRAY_BUFFER,
            (vertices.len() * mem::size_of::<GLfloat>()) as GLsizeiptr,
            &vertices[0] as *const f32 as *const c_void,
            gl::STATIC_DRAW,
        );

        let stride = 3 * mem::size_of::<GLfloat>() as GLsizei;

        gl::VertexAttribPointer(0, 3, gl::FLOAT, gl::FALSE, stride, ptr::null());
        gl::EnableVertexAttribArray(0);

        let mut light_VAO = 0;
        gl::GenVertexArrays(1, &mut light_VAO);
        gl::BindVertexArray(light_VAO);

        gl::BindBuffer(gl::ARRAY_BUFFER, VBO);

        gl::VertexAttribPointer(0, 3, gl::FLOAT, gl::FALSE, stride, ptr::null());
        gl::EnableVertexAttribArray(0);

        (light_shader, lamp_shader, VBO, cube_VAO, light_VAO)
    };

    while !window.should_close() {
        let current_frame = glfw.get_time() as f32;
        delta_time = current_frame - last_frame;
        last_frame = current_frame;

        process_events(&events, &mut first_mouse, &mut last_x, &mut last_y, &mut camera);

        process_input(&mut window, delta_time, &mut camera);
        unsafe {
            gl::ClearColor(0.2, 0.3, 0.3, 1.0);
            gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT);

            light_shader.use_program();
            light_shader.set_vec3(c_str!("object_color"), 1., 0.5, 0.31);
            light_shader.set_vec3(c_str!("light_color"), 1., 1., 1.);

            let projection: Matrix4<f32> =
                perspective(Deg(camera.zoom), SCR_WIDTH as f32 / SCR_HEIGHT as f32, 0.1, 100.);
            light_shader.set_mat4(c_str!("projection"), &projection);
            let view: Matrix4<f32> = camera.get_view_matrix();
            light_shader.set_mat4(c_str!("view"), &view);

            let mut model = Matrix4::<f32>::identity();
            light_shader.set_mat4(c_str!("model"), &model);

            gl::BindVertexArray(cube_VAO);
            gl::DrawArrays(gl::TRIANGLES, 0, 36);

            lamp_shader.use_program();
            lamp_shader.set_mat4(c_str!("projection"), &projection);
            lamp_shader.set_mat4(c_str!("view"), &view);
            model = Matrix4::from_translation(light_position);
            model = model * Matrix4::from_scale(0.2);
            lamp_shader.set_mat4(c_str!("model"), &model);
            gl::BindVertexArray(light_VAO);
            gl::DrawArrays(gl::TRIANGLES, 0, 36);
        }
        window.swap_buffers();
        glfw.poll_events();
    }

    unsafe {
        gl::DeleteVertexArrays(1, &cube_VAO);
        gl::DeleteVertexArrays(1, &light_VAO);
        gl::DeleteBuffers(1, &VBO);
    }
}
